﻿using Identity.Api.Extensions;
using Identity.Api.Models;
using Identity.API;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.IO.Compression;
using System.Text.RegularExpressions;

namespace Identity.Api.Data;
public class ApplicationDbContextSeed
{
    private readonly IPasswordHasher<ApplicationUser> _passwordHasher = new PasswordHasher<ApplicationUser>();

    public async Task SeedAsync(ApplicationDbContext context, IWebHostEnvironment env,
        ILogger<ApplicationDbContextSeed> logger, IOptions<AppSettings> settings, int? retry = 0)
    {
        int retryForAvaiability = retry.Value;

        try
        {
            var useCustomizationData = settings.Value.UseCustomizationData;
            var contentRootPath = env.ContentRootPath;
            var webroot = env.WebRootPath;

            if (!context.Users.Any())
            {
                context.Users.AddRange(useCustomizationData
                    ? GetUsersFromFile(contentRootPath, logger)
                    : GetDefaultUser());

                await context.SaveChangesAsync();
            }

            if (useCustomizationData)
            {
                GetPreconfiguredImages(contentRootPath, webroot, logger);
            }
        }
        catch (Exception ex)
        {
            if (retryForAvaiability < 10)
            {
                retryForAvaiability++;

                logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext));

                await SeedAsync(context, env, logger, settings, retryForAvaiability);
            }
        }
    }

    private IEnumerable<ApplicationUser> GetUsersFromFile(string contentRootPath, ILogger logger)
    {
        string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv");

        if (!File.Exists(csvFileUsers))
        {
            return GetDefaultUser();
        }

        string[] csvheaders;
        try
        {
            string[] requiredHeaders = {
                    "zone", "province", "isactive","tenancyName", "city", "country",
                    "email", "isdeleted", "lastname", "name", "phonenumber",
                    "username", "creationTime","creatorUserId","deletionTime","deleterUserId","lastModificationTime","lastModifierUserId",
                    "normalizedemail", "normalizedusername", "password"
                };
            csvheaders = GetHeaders(requiredHeaders, csvFileUsers);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message);

            return GetDefaultUser();
        }

        List<ApplicationUser> users = File.ReadAllLines(csvFileUsers)
                    .Skip(1) // skip header column
                    .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
                    .SelectTry(column => CreateApplicationUser(column, csvheaders))
                    .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; })
                    .Where(x => x != null)
                    .ToList();

        return users;
    }

    private ApplicationUser CreateApplicationUser(string[] column, string[] headers)
    {
        if (column.Count() != headers.Count())
        {
            throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'");
        }

        string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim('"').Trim();
        if (!int.TryParse(cardtypeString, out int cardtype))
        {
            throw new Exception($"cardtype='{cardtypeString}' is not a number");
        }

        var user = new ApplicationUser
        {
            Zone = column[Array.IndexOf(headers, "zone")].Trim('"').Trim(),
            Province = column[Array.IndexOf(headers, "province")].Trim('"').Trim(),
            City = column[Array.IndexOf(headers, "city")].Trim('"').Trim(),
            Country = column[Array.IndexOf(headers, "country")].Trim('"').Trim(),
            Email = column[Array.IndexOf(headers, "email")].Trim('"').Trim(),
            CreationTime = Convert.ToDateTime(column[Array.IndexOf(headers, "creationTime")].Trim('"').Trim()),
            CreatorUserId = Convert.ToInt32(column[Array.IndexOf(headers, "creatorUserId")].Trim('"').Trim()),
            DeletionTime = Convert.ToDateTime(column[Array.IndexOf(headers, "deletionTime")].Trim('"').Trim()),
            DeleterUserId = Convert.ToInt32(column[Array.IndexOf(headers, "deleterUserId")].Trim('"').Trim()),
            LastModificationTime = Convert.ToDateTime(column[Array.IndexOf(headers, "lastModificationTime")].Trim('"').Trim()),
            LastModifierUserId = Convert.ToInt32(column[Array.IndexOf(headers, "lastModifierUserId")].Trim('"').Trim()),
            TenancyName = column[Array.IndexOf(headers, "tenancyName")].Trim('"').Trim(),
            Id = Guid.NewGuid().ToString(),
            IsActive = Convert.ToBoolean(column[Array.IndexOf(headers, "isactive")].Trim('"').Trim()),
            IsDeleted = Convert.ToBoolean(column[Array.IndexOf(headers, "isdeleted")].Trim('"').Trim()),
            PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim('"').Trim(),
            UserName = column[Array.IndexOf(headers, "username")].Trim('"').Trim(),
            NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim('"').Trim(),
            NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim('"').Trim(),
            SecurityStamp = Guid.NewGuid().ToString("D"),
            PasswordHash = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password
            Password = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password
        };

        user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash);

        return user;
    }

    private IEnumerable<ApplicationUser> GetDefaultUser()
    {
        var user =
        new ApplicationUser()
        {
            IsActive = true,
            Province = "四川省",
            TenancyName = "1211103011",
            City = "成都市",
            Zone = "武侯区",
            Country = "Zh",
            Email = "User@qq.com",
            Id = Guid.NewGuid().ToString(),
            PhoneNumber = "1234567890",
            UserName = "User",
            NormalizedEmail = "USER@QQ.COM",
            NormalizedUserName = "USER@QQ.COM",
            SecurityStamp = Guid.NewGuid().ToString("D"),
            Password = "111111",
        };

        user.PasswordHash = _passwordHasher.HashPassword(user, user.Password);

        return new List<ApplicationUser>()
            {
                user
            };
    }

    static string[] GetHeaders(string[] requiredHeaders, string csvfile)
    {
        string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');

        if (csvheaders.Count() != requiredHeaders.Count())
        {
            throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'");
        }

        foreach (var requiredHeader in requiredHeaders)
        {
            if (!csvheaders.Contains(requiredHeader))
            {
                throw new Exception($"does not contain required header '{requiredHeader}'");
            }
        }

        return csvheaders;
    }

    static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger logger)
    {
        try
        {
            string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
            if (!File.Exists(imagesZipFile))
            {
                logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile);
                return;
            }

            string imagePath = Path.Combine(webroot, "images");
            string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();

            using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
            {
                foreach (ZipArchiveEntry entry in zip.Entries)
                {
                    if (imageFiles.Contains(entry.Name))
                    {
                        string destinationFilename = Path.Combine(imagePath, entry.Name);
                        if (File.Exists(destinationFilename))
                        {
                            File.Delete(destinationFilename);
                        }
                        entry.ExtractToFile(destinationFilename);
                    }
                    else
                    {
                        logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ;
        }
    }
}
